;; The first three lines of this file were inserted by DrScheme. They record metadata
;; about the language level of this file in a form that our tools can easily process.
#reader(lib "htdp-advanced-reader.ss" "lang")((modname pickgame-universe) (read-case-sensitive #t) (teachpacks ((lib "universe.ss" "teachpack" "2htdp"))) (htdp-settings #(#t constructor repeating-decimal #t #t none #f ((lib "universe.ss" "teachpack" "2htdp")))))
;; this file contains the code for the universe for the pickgame.

#| DESCRIPTION OF THE GAME:
The Pickgame consists of a row of 20 blocks. Every player can see this row of
blocks (1-4 people can play). Each player picks a color. On their turn, they
pick one of the unfilled blocks to put their color in. The next player does
the same. The winner is the person who has the most consecutive blocks.
|#


;; DATA DEFINITIONS:

;; THE UNIVERSE:

;; a universe is a (list [listof box-state] [listof players])
;; A universe should never be made directly. Instead, use the following functions
;; to create and get information about a universe. This is done because we must pass
;; an s-expression to the worlds making up the universe. If we represent data in this
;; manner, we can easily send the universe to clients.

;; FUNCTIONS FOR THE UNIVERSE

;; make-universe : [listof box-state] [listof players] [listof world] -> universe
;; makes a universe from the state of the game and the current list of players.
(define (make-universe lobs lop)
  (list lobs lop))

(check-expect (make-universe box-list-1 player-list-2)
	      (list box-list-1 player-list-2))
(check-expect (make-universe box-list-2 player-list-2)
	      (list box-list-2 player-list-2))

;; universe-players : universe -> [listof players]
;; extracts the list of players from the given universe.
(define (universe-players uni)
  (second uni))

(check-expect (universe-players uni-1) player-list-2)
(check-expect (universe-players uni-2) player-list-2)

;; universe-boxes : universe -> [listof box-state]
;; extracts the current game board from the given universe.
(define (universe-boxes uni)
  (first uni))

(check-expect (universe-boxes uni-1) box-list-1)
(check-expect (universe-boxes uni-2) box-list-2)

;; A player is a symbol representing a color, which is the color
;; of the player on the game board.

;; a box-state is either:
; - a player
; - 'unowned
;; the former represents a square occupied by a player and the latter
;; represents a square occupied by nobody.

;; a gameboard is a list of box states.

;; SAMPLE DATA

;; sample player-lists
(define player-list-1 '(red blue green yellow))
(define player-list-2 '(red blue))

;; sample box-lists
(define box-list-1 '(unowned unowned unowned unowned unowned))
(define box-list-2 '(red     unowned blue    unowned red))

;; sample universes
(define uni-1 (make-universe box-list-1 player-list-2))
(define uni-2 (make-universe box-list-2 player-list-2))

;; STARTING DATA: DO NOT MODIFY

;; the base list of colors to draw from
(define BASE-LIST-COLORS '(red blue green yellow))

;; the maximum number of players
(define BASE-MAXIMUM-PLAYERS 2)

;; the base list of players
(define BASE-LIST-PLAYERS empty)

;; the length of the base list of the game
(define BASE-LENGTH 10)

;; the base list of the game - a list comprising entirely of unowned boxes
(define BASE-LIST-BOXES (build-list BASE-LENGTH (lambda (x) 'unowned)))

;; the base universe
(define BASE-UNIVERSE (make-universe BASE-LIST-BOXES BASE-LIST-PLAYERS))

;;;;;;;;;;;;;;;;;;; EVENTS CONTROLLED BY THE UNIVERSE ;;;;;;;;;;;;;;;;;;;;;;;;;
;; only the events that we actually will use are detailed here

;; new-client : [listof world] universe world -> bundle
;; handles a new client connecting to this universe.
(define (new-client low uni new-world)
  (cond [(>= (num-current-players uni) BASE-MAXIMUM-PLAYERS) (make-bundle low uni empty)]
        [else (local ((define new-player (make-new-player (num-current-players uni)))
                      (define new-low (cons new-world low))
                      (define new-uni (add-player new-player uni))
                      (define new-player-msg (msg-new-player new-player new-world))
                      (define msg-rest (update-all low new-uni)))
                (make-bundle new-low new-uni (cons new-player-msg msg-rest)))]))


;; make-new-player : number -> player
;; makes a new player, assigning the player a color based on the number of players
;; currently in the universe.
(define (make-new-player num)
  (make-new-player-real num BASE-LIST-COLORS))

(define (make-new-player-real num loc)
  (cond [(zero? num) (first loc)]
        [else (make-new-player-real (sub1 num)
                                    (rest loc))]))

(check-expect (make-new-player 0) (first BASE-LIST-COLORS))
(check-expect (make-new-player 1) (second BASE-LIST-COLORS))

;; add-player : player universe -> universe
;; adds the given player to the given universe.
(define (add-player p uni)
  (make-universe (universe-boxes uni) (cons p (universe-players uni))))


;; msg-new-player : player -> player
;; makes a message to a new player containing their color/name.
(define (msg-new-player player world)
  (make-mail world (list player)))

;(check-expect (msg-new-player 'red) (make-mail 'red 'red))

;; new-message : [listof world] universe world sexp -> bundle
;; handles receiving a new message from the given client.
(define (new-message low uni world msg)
  (cond [;(or  (not (symbol=? world (current-player uni)))
              (not (valid-msg? msg))
         (make-bundle low uni empty)]
        [(world=? (first low) world)
         (local [(define new-uni (handle-placement uni (first (universe-players uni)) (first msg)))
                      (define new-mails (update-all low new-uni))
                      (define new-low (next-player low))]
                     (make-bundle new-low new-uni new-mails))]
        [else (make-bundle low uni empty)]))
         


;; valid-msg : sexp -> boolean
;; returns true if the message is valid. ONLY checks the message body itself
;; for validity; does not actually determine if the message was sent by the
;; current player.
(define (valid-msg? msg)
  (and (cons? msg)
       (empty? (rest msg))
       (number? (first msg))
       (<= (first msg) BASE-LENGTH)))

(check-expect (valid-msg? '(5)) true)
(check-expect (valid-msg? '(50000)) false)
(check-expect (valid-msg? '(red)) false)
(check-expect (valid-msg? '(orange)) false)

;; add-player : player universe -> universe
;; adds a new player to the given universe.

;; update-all : [listof world] universe -> [listof mail]
;; makes a list of mails containing the state of te universe to all worlds
;; connected to this universe.
(define (update-all low uni)
  (cond [(empty? low) empty]
        [else (cons (make-mail (first low) uni)
                    (update-all (rest low) uni))]))

;; handle-placement : universe player number -> universe
;; handles a player placing a square of a certain color,
;; and switching to the next player subsequently.
(define (handle-placement uni p n)
  (local [(define valid-move (valid-square? n (universe-boxes uni)))]
         (cond [valid-move (make-universe (add-piece n p (universe-boxes uni))
                                     (next-player (universe-players uni)))]
               [else uni])))

;; next-player : [listof player] -> [listof player]
;; rotates the list of players so that it is the next one's turn.
(define (next-player lop)
  (append (rest lop)
          (list (first lop))))

(check-expect (next-player '(red blue)) '(blue red))
(check-expect (next-player '(blue green yellow)) '(green yellow blue))

;; current-player : uni -> player
;; gets the current player from the list of worlds.
(define (current-player uni)
  (first (universe-players uni)))

(check-expect (current-player uni-1) 'red)
(check-expect (current-player uni-2) 'red)

;; num-current-players : uni -> number;
;; gets the number of current players contained within the universe.
(define (num-current-players uni)
  (length (universe-players uni)))

(check-expect (num-current-players BASE-UNIVERSE) 0)
(check-expect (num-current-players uni-1) 2)
(check-expect (num-current-players uni-2) 2)

;; valid-square? : number [listof box-state] -> boolean
;; determines if the square at the given location is a valid location to place
;; a box on.
(define (valid-square? n lob)
  (cond [(and (= n 1) (cons? lob))
         (symbol=? (first lob) 'unowned)]
        [(empty? lob) false]
        [else (valid-square? (- n 1) (rest lob))]))

(check-expect (valid-square? 1 box-list-2) false)
(check-expect (valid-square? 1 BASE-LIST-BOXES) true)

;; add-piece : number player [listof box-state] -> [listof box-state]
;; adds a piece to the game board. THIS SHOULD ONLY BE CALLED IF IT IS
;; POSSIBLE TO ADD THE PIECE TO THE BOARD.
(define (add-piece n player lob)
  (cond [(= n 1) (cons player (rest lob))]
        [else (cons (first lob)
                    (add-piece (sub1 n) player (rest lob)))]))

(check-expect (add-piece 1 'red '(unowned unowned)) '(red unowned))
(check-expect (add-piece 2 'blue '(unowned unowned)) '(unowned blue))

;; this code will create a new universe.
(universe BASE-UNIVERSE (on-new new-client)
                        (on-msg new-message))